/*
 *  This file is part of Social Media Monitoring Toolbox.
 *
 *  Social Media Monitoring Toolbox is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Social Media Monitoring Toolbox is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Social Media Monitoring Toolbox. If not, see <http://www.gnu.org/licenses/lgpl.html>.
 *
 * � Copyright 2013
 * Jana Asmussen, Julian Bau�, Daniela Dalock, Christian Gutjahr,
 * Fabian Heidorn, Alexander Kaack, Vitali Kagadij 
 * 
 */

package logic;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.TreeMap;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;

import org.xml.sax.SAXException;

import types.*;

import data.*;


public class Logic implements ILogic,Serializable {
	
	private static final long serialVersionUID = 3450156490666774303L;
	IData data = new Data();

	public Logic() {
	}

	/**
	 * Returns a list of all StreamDescriptors the database knows. These
	 * StreamDescriptors can be used to to get all posts which are referenced by
	 * those.
	 * 
	 * @return List of all StreamDescriptors
	 */
	@Override
	public List<StreamDescriptor> getAllStreamDescriptor() {
		List<StreamDescriptor> result = new ArrayList<StreamDescriptor>();
		result = data.getStreams();
		return result;
	}

	/**
	 * Returns a list of all StreamDescriptors of TweetPosts the database knows.
	 * These StreamDescriptors can be used to to get all posts which are
	 * referenced by those.
	 * 
	 * @return List of all StreamDescriptors for TweetPosts
	 */
	@Override
	public List<StreamDescriptor> getAllTwitterStreamDescriptor() {
		List<StreamDescriptor> result = new ArrayList<StreamDescriptor>();
		List<StreamDescriptor> allStreams = getAllStreamDescriptor();
		for (StreamDescriptor sd : allStreams) {
			if (sd.getElementType() == TweetPost.class)
				result.add(sd);
		}
		return result;
	}

	/**
	 * Returns a list of all StreamDescriptors of YoutubeVideoTypes the database
	 * knows. These StreamDescriptors can be used to to get all posts which are
	 * referenced by those.
	 * 
	 * @return List of all StreamDescriptors for YoutubeVideoTypes
	 */
	@Override
	public List<StreamDescriptor> getAllYoutubeStreamDescriptor() {
		List<StreamDescriptor> result = new ArrayList<StreamDescriptor>();
		List<StreamDescriptor> allStreams = getAllStreamDescriptor();
		for (StreamDescriptor sd : allStreams) {
			if (sd.getElementType() == YoutubeVideoType.class)
				result.add(sd);
		}
		return result;
	}

	/**
	 * Returns a List of all TweetPosts the database knows, for a specific
	 * StreamDescriptor.
	 * 
	 * @param streamDes StreamDescriptor the database knows
	 * @param filter FilterType to limit the result
	 * @return List of TweetPosts
	 */
	@Override
	public List<TweetPost> getTweetPosts(StreamDescriptor streamDes, FilterTyp filter) {
		List<TweetPost> result = new ArrayList<TweetPost>();
		if (streamDes.getElementType() != TweetPost.class) {
			System.out.println("StreamDescriptor beschreibt keine TweetPosts!");
		} else {
			result = data.getTweetPosts(streamDes, filter);
		}
		return result;
	}

	/**
	 * Returns a List of all YoutubeVideoTypes the database knows, for a
	 * specific StreamDescriptor. The streamDes argument must be available in
	 * the database. The filter argument can limit the results.
	 * 
	 * @param streamDes StreamDescriptor the database knows
	 * @param filter FilterType to limit the result
	 * @return List of YoutubeVideoTypes
	 */
	@Override
	public List<YoutubeVideoType> getYouTubeVideos(StreamDescriptor streamDes, FilterTyp filter) {
		List<YoutubeVideoType> result = new ArrayList<YoutubeVideoType>();
		if (streamDes.getElementType() != YoutubeVideoType.class) {
			System.out.println("StreamDescriptor beschreibt keine YoutubeVideos!");
		} else {
			result = data.getYouTubeVideos(streamDes, filter);
		}
		return result;
	}

	
	/*
	 * Returns a List of the TweetPost sorted by the number of retweets.
	 * 
	 * @return List of sorted TweetPost
	 
	public List<TweetPost> getTopTweetPosts() {
		List<TweetPost> result = new ArrayList<TweetPost>();
		List<StreamDescriptor> allStreams = getAllStreamDescriptor();
		for (StreamDescriptor sDes : allStreams) {
			
			if(sDes.getElementType() == TweetPost.class){
			List<TweetPost> tweets = getTweetPosts(sDes, null);
			for (TweetPost t : tweets) {
				result.add(t);
			}
			}
		}
		Collections.sort(result);
		return result;
	}
	*/
	
	/**
	 * Returns a List of all twitter posts the database contains.
	 * 
	 * @param filter FilterTyp to limit the result
	 * @return List of TweetPost
	 */
	public List<TweetPost> getAllTweetPost(){
		List<TweetPost> result = new ArrayList<TweetPost>();
		List<StreamDescriptor> allStreams = getAllStreamDescriptor();
		for (StreamDescriptor sDes : allStreams) {
			if(sDes.getElementType() == TweetPost.class){
				List<TweetPost> tweets = getTweetPosts(sDes, null);
				for (TweetPost t : tweets) {
					result.add(t);
				}
			}
		}
		return result;
	}
	
	/**
	 * This method returns a list of YoutubeVideoTypes sorted by views
	 * 
	 * @return List of YoutubeVideoType
	 */
	public List<YoutubeVideoType> getTopYoutubePosts() {
		List<YoutubeVideoType> result = new ArrayList<YoutubeVideoType>();
		List<StreamDescriptor> stream = getAllStreamDescriptor();
		for (StreamDescriptor sDes : stream) {
			if(sDes.getElementType() == YoutubeVideoType.class){
				List<YoutubeVideoType> youtubeVideos = getYouTubeVideos(sDes, null);
				for (YoutubeVideoType yt : youtubeVideos) {
					result.add(yt);
				}
			}
		}
		Collections.sort(result);
		return result;
	}

	/**
	 * This method updates all TweetPosts for a stated analysis. The analysis
	 * argument specifies the analysis. The updated TweetPosts are saved in the
	 * database after executing the analysis.
	 * 
	 * @param analysis AnaylseTyp to specify the analysis
	 */
	@Override
	public void updateTweetPosts(AnalyseTyp analysis) {
		System.out.println("Start 'updateTweetPosts' ...");
		// tweets = new ArrayList<TweetPost>();
		if (analysis.sentimentAnalysis) {
			List<StreamDescriptor> sDes = getAllTwitterStreamDescriptor();
			for (StreamDescriptor i : sDes) {
				List<TweetPost> tweets = getTweetPosts(i, null);
				for (TweetPost t : tweets) {
					if (t.getSentiment() == -2) {
						int sentiment = executeSentiment(t, i);
						t.setSentiment(sentiment);
						data.updatePost(t);
					}
				}
			}
		}
		System.out.println("End 'updateTweetPosts'!");
	}

	/**
	 * Returns an integer. This method executes the sentiment analysis for a
	 * given TweetPost and his StreamDescriptor. The tp and the streamDes
	 * argument have to be available in the database.
	 * 
	 * @param tp TweetPost for which the sentiment analysis should be made
	 * @param streamDes StreamDescriptor which identifies the TweetPost
	 * @return Integer which declares the sentiment value (1,-1,0)
	 */
	public int executeSentiment(TweetPost tp, StreamDescriptor streamDes) {
		int sentiment = -2;
		String text = tp.getTweetText();
		if(streamDes.getElementType() != TweetPost.class){
			System.out.println("StreamDescriptor beschreibt keine TweetPosts!");
			return sentiment;
		}
		String target = streamDes.getTrackedTags()[0];
		try {
			sentiment = SentimentAnalysis
					.getSentimentFromDocument(SentimentAnalysis
							.getTargetedSentiment(text, target));
		} catch (FileNotFoundException | XPathExpressionException
				| SAXException | ParserConfigurationException e) {
			e.printStackTrace();
		} catch (IOException e) {
			if (e.getMessage().contains("cannot-locate-keyphrase")) {
				try {
					sentiment = SentimentAnalysis
							.getSentimentFromDocument(SentimentAnalysis
									.getSentiment(text));
				} catch (FileNotFoundException | XPathExpressionException
						| SAXException | ParserConfigurationException e1) {
					e1.printStackTrace();
				} catch (IOException e1) {
					if (e1.getMessage().contains("unsupported-text-language")) {
						System.out.println("Unsupported test language");
					}
				}
			}
		}
		return sentiment;
	}

	/**
	 * Returns an array with 3 double values. Those values represents the
	 * percentage values for each mood of TweetPosts or YoutubeVideoTypes
	 * (positive, negative and neutral) specified by the StreamDescriptor. The
	 * streamDes argument must be available in the database.
	 * <p>
	 * result[0]: the percentage value of the positive mood <br>
	 * result[1]: the percentage value of the negative mood <br>
	 * result[2]: the percentage value of the neutral mood <br>
	 * 
	 * @param streamDes StreamDescriptor the database knows
	 * @return Array with 3 double values
	 */
	public double[] getPercentageSentiment(StreamDescriptor streamDes,FilterTyp filter) {
		double result[] = { 0, 0, 0 };
		double posCount = 0.0;
		double negCount = 0.0;
		double neutCount = 0.0;
		double postCount = 0.0;

		if (streamDes.getElementType() == TweetPost.class) {
			List<TweetPost> tweets = getTweetPosts(streamDes, filter);
			if(tweets.isEmpty()){
				result[0] = -1;
				return result;
			}
			for (TweetPost t : tweets) {
				if (t.getSentiment() == 1)
					posCount++;
				if (t.getSentiment() == -1)
					negCount++;
				if (t.getSentiment() == 0)
					neutCount++;
			}
		}
		if (streamDes.getElementType() == YoutubeVideoType.class) {
			List<YoutubeVideoType> videotypes = getYouTubeVideos(streamDes,
					filter);
			if(videotypes.isEmpty()){
				result[0] = -1;
				return result;
			}
			for (YoutubeVideoType y : videotypes) {
				if (y.getLikes() > y.getDislikes())
					posCount++;
				if (y.getLikes() < y.getDislikes())
					negCount++;
				if (y.getLikes() == y.getDislikes())
					neutCount++;
			}
		}
		postCount = posCount + negCount + neutCount;
		// System.out.println("PostCount: "+postCount);
		result[0] = (posCount / postCount) * 100;
		result[1] = (negCount / postCount) * 100;
		result[2] = (neutCount / postCount) * 100;
		return result;
	}

	/**
	 * Returns a HashMap with GeoTags and Strings for a given StreamDescriptor.
	 * The streamDes argument must be available in the database.
	 * 
	 * @param streamDes StreamDescriptor the database knows
	 * @return HashMap of GeoTags and Strings associated with the given StreamDescriptor
	 */
	public HashMap<GeoTag, String> getAllGeoTags(StreamDescriptor streamDes) {
		HashMap<GeoTag, String> result = new HashMap<GeoTag, String>();

		if (streamDes.getElementType() == TweetPost.class) {
			List<TweetPost> tweets = getTweetPosts(streamDes, null);
			for (TweetPost t : tweets) {
				if (t.getGeoTag() != null)
					result.put(t.getGeoTag(), t.getTweetText());
			}
		}
		if (streamDes.getElementType() == YoutubeVideoType.class) {
			List<YoutubeVideoType> videos = getYouTubeVideos(streamDes, null);
			for (YoutubeVideoType y : videos) {
				if (y.getGeoTag() != null)
					result.put(y.getGeoTag(), y.getVideoTitle());
			}
		}
		return result;
	}

	/**
	 * Returns a HashMap with GeoTags and Strings for a given StreamDescriptor
	 * and integer. The integer represents the mood for which the GeoTags are
	 * filtered. The streamDes argument must be available in the database
	 * <p>
	 * integer value "1" - returns the GeoTags for TweetPosts or YouTubeVideos with positive mood <br>
	 * integer value "-1" - returns the GeoTags for TweetPosts or YouTubeVideos with negative mood <br>
	 * integer value "0" - returns the GeoTags for TweetPosts or YouTubeVideos with neutral mood <br>
	 * Other values will be ignored.
	 * 
	 * @param streamDes StreamDescriptor the database knows
	 * @param sentiment Integer value to filter the GeoTags
	 * @return HashMap of GeoTags and Strings associated with the given StreamDescriptor and the given integer value
	 */
	public HashMap<GeoTag, String> getGeoTagsWithSentiment(StreamDescriptor streamDes, int sentiment) {
		HashMap<GeoTag, String> result = new HashMap<GeoTag, String>();

		if (sentiment == 1) {
			if (streamDes.getElementType() == TweetPost.class) {
				List<TweetPost> tweets = getTweetPosts(streamDes, null);
				for (TweetPost t : tweets) {
					if (t.getSentiment() == 1) {
						if (t.getGeoTag() != null)
							result.put(t.getGeoTag(), t.getTweetText());
					}
				}
			}
			if (streamDes.getElementType() == YoutubeVideoType.class) {
				List<YoutubeVideoType> videos = getYouTubeVideos(streamDes,
						null);
				for (YoutubeVideoType y : videos) {
					if (y.getLikes() > y.getDislikes()) {
						if (y.getGeoTag() != null)
							result.put(y.getGeoTag(), y.getVideoTitle());
					}
				}
			}
		} else if (sentiment == -1) {
			if (streamDes.getElementType() == TweetPost.class) {
				List<TweetPost> tweets = getTweetPosts(streamDes, null);
				for (TweetPost t : tweets) {
					if (t.getSentiment() == -1) {
						if (t.getGeoTag() != null)
							result.put(t.getGeoTag(), t.getTweetText());
					}
				}
			}
			if (streamDes.getElementType() == YoutubeVideoType.class) {
				List<YoutubeVideoType> videos = getYouTubeVideos(streamDes,
						null);
				for (YoutubeVideoType y : videos) {
					if (y.getDislikes() > y.getLikes()) {
						if (y.getGeoTag() != null)
							result.put(y.getGeoTag(), y.getVideoTitle());
					}
				}
			}
		} else if (sentiment == 0) {
			if (streamDes.getElementType() == TweetPost.class) {
				List<TweetPost> tweets = getTweetPosts(streamDes, null);
				for (TweetPost t : tweets) {
					if (t.getSentiment() == 0) {
						if (t.getGeoTag() != null)
							result.put(t.getGeoTag(), t.getTweetText());
					}
				}
			}
			if (streamDes.getElementType() == YoutubeVideoType.class) {
				List<YoutubeVideoType> videos = getYouTubeVideos(streamDes,
						null);
				for (YoutubeVideoType y : videos) {
					if (y.getDislikes() == y.getLikes()) {
						if (y.getGeoTag() != null)
							result.put(y.getGeoTag(), y.getVideoTitle());
					}
				}
			}
		}
		return result;
	}

	/**
     * Returns a TreeMap of Dates and Doubles, each describing the average
     * sentiment for an period. This map is sorted by date.
     * 
     * @param streamDes StreamDescriptor to describe the analyzed posts
     * @param startDate Date to be started with
     * @param endDate Date to declare the end of the timeframe
     * @param interval Integer to describe the length of one period in days
     * @return TreeMap of Dates and Doubles
     */
    public TreeMap<Date,Double> getAverageSentimentForPeriod(StreamDescriptor streamDes, Date startDate, Date endDate, int interval) {
            int i = 0;
            double counter = 0.0;
            double preResult = 0.0;
            double average = 0.0;
            
            TreeMap<Date,Double> result = new TreeMap<Date,Double>();
            FilterTyp filter = new FilterTyp();
            ValueRange vr = null;
            try {
                    Thread.sleep(1500);
            } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
            }
            for (Date dateRunner = startDate; !((dateRunner.compareTo(endDate)==0) || dateRunner.after(endDate)); i++) {
                    counter = 0.0;
                    Date start = dateRunner;
                    dateRunner = addDays(dateRunner, interval);
                    vr = new ValueRange(start, dateRunner);
                    
                    filter.setLimitTime(vr);
                    
                    preResult = getAverageSentiment(streamDes, filter);
                    if(preResult != -11.0){
                            result.put(start, preResult);
                    }else
                            result.put(start, -11.0);
            }
            return result;
    }
	
    /**
     * This method returns the average sentiment for specific TweetPosts selected by the streamDes and filter parameter.
     * 
     * @param streamDes StreamDescriptor the database knows
     * @param filter FilterTyp to select TweetPosts
     * @return Double value which declares the average sentiment
     */
	private double getAverageSentiment(StreamDescriptor streamDes, FilterTyp filter) {
		int counter = 0;
		int posCount = 0;
		int negCount = 0;
		if (streamDes.getElementType() == TweetPost.class) {
			List<TweetPost> tweets = getTweetPosts(streamDes, filter);
			if(tweets.isEmpty()){
				return -11.0;
			}
			for (TweetPost tweet : tweets) {
				if (tweet.getSentiment() == 1)
					posCount++;
				if (tweet.getSentiment() == -1)
					negCount++;
			}
		counter = tweets.size();
		}else if(streamDes.getElementType() == YoutubeVideoType.class) {
			List<YoutubeVideoType> videotypes = getYouTubeVideos(streamDes,filter);
			if(videotypes.isEmpty()){
				return -11.0;
			}
			for (YoutubeVideoType clip : videotypes) {
				if (clip.getLikes() > clip.getDislikes())
					posCount++;
				if (clip.getLikes() < clip.getDislikes())
					negCount++;
			}
		counter = videotypes.size();			
		}
		return ((double)(posCount-negCount)/counter);
	}

	/**
	 * adds Days to an given Date. The integer value defines the number of days
	 * to be added.
	 * 
	 * @param dt starting Date
	 * @param days Days to be added
	 * @return Date
	 */
	public Date addDays(Date dt, int days) {
		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(dt);
		cal.add(Calendar.DATE, days);
		return cal.getTime();
	}
}
